
#include <apr_portable.h>
#include "APRInterface.h"
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/evp.h>
#include <openssl/engine.h>
#include <openssl/conf.h>
#include "debug.h"
#include "SSLInterface.h"

SSLInterface *SSLInterface::ssl_iface_singleton = NULL;

/** Get singleton instance (prevents double initialisation) */
SSLInterface &SSLInterface::getInstance()
{
   if (ssl_iface_singleton == NULL)
      ssl_iface_singleton = new SSLInterface();
   return *ssl_iface_singleton;
}

#if APR_HAS_THREADS
apr_pool_t *ssl_pool;
apr_thread_mutex_t **ssl_locks;

/** A structure redefinition to allow the SSL library to use this as the lock
 * reference for locking the cryptography mechanisms in SSL. */
typedef struct CRYPTO_dynlock_value
{
   apr_thread_mutex_t *lock;
} CRYPTO_dynlock_value;

/** Creates a dynamic SSL lock. This is provided to the SSL library via a
 * callback to ensure thread safety. */
static CRYPTO_dynlock_value *ssl_dyn_create(const char *file, int line)
{
   CRYPTO_dynlock_value *l;
   apr_status_t rv;

   l = (CRYPTO_dynlock_value *)apr_palloc(ssl_pool, sizeof(CRYPTO_dynlock_value));
   rv = apr_thread_mutex_create(&(l->lock), APR_THREAD_MUTEX_DEFAULT, ssl_pool);
   if (rv != APR_SUCCESS)
   {
      errprint("failed to create mutex for dynamic lock");
      return NULL;
   }
   return l;
}

/** Lock the given dynamic lock. This is provided to the SSL library via a
 * callback to ensure thread safety. */
static void ssl_dyn_lock(int mode, CRYPTO_dynlock_value *l, const char *file, int line)
{
   if (mode & CRYPTO_LOCK)
      apr_thread_mutex_lock(l->lock);
   else
      apr_thread_mutex_unlock(l->lock);
}

/** Destroy the given dynamic lock. This is provided to the SSL library via a
 * callback to ensure thread safety. */
static void ssl_dyn_destroy(CRYPTO_dynlock_value *l, const char *file, int line)
{
   apr_thread_mutex_destroy(l->lock);
}

/** Lock one of the many dynamic locks in the general array. This is provided
 * to the SSL library via a callback to ensure thread safety. */
static void ssl_lock(int mode, int n, const char *file, int line)
{
   if (mode & CRYPTO_LOCK)
      apr_thread_mutex_lock(ssl_locks[n]);
   else
      apr_thread_mutex_unlock(ssl_locks[n]);
}

/** Get the thread ID of the current thread. This is provided
 * to the SSL library via a callback to ensure thread safety. */
static unsigned long ssl_id(void)
{
#ifdef __MVS__
   struct PSA {
      char unmapped[540];
      unsigned long PSATOLD;
   } *psaptr = 0;

   return psaptr->PSATOLD;
#else
   return (unsigned long) apr_os_thread_current();
#endif
}

/** Cleanup bits the SSL has allocated. This is provided
 * to the SSL library via a callback to ensure thread safety.
 * The upshot of this it seems is that you never have to
 * explicitly destroy SSL. */
static apr_status_t cleanup_ssl(void *data)
{
   CRYPTO_set_locking_callback(NULL);
   CRYPTO_set_id_callback(NULL);
   CRYPTO_set_dynlock_create_callback(NULL);
   CRYPTO_set_dynlock_lock_callback(NULL);
   CRYPTO_set_dynlock_destroy_callback(NULL);

   CONF_modules_unload(1);
   EVP_cleanup();
   ENGINE_cleanup();
   CRYPTO_cleanup_all_ex_data();
   ERR_remove_state(0);
   ERR_free_strings();
   return APR_SUCCESS;
}

#endif

/** Has SSL initialised? */
static int have_init_ssl = 0;

/** Initialise the SSL libraries. */
static void init_ssl_libraries(void)
{
   if (!have_init_ssl)
   {
#if APR_HAS_THREADS
      int i, numlocks;
#endif
      CRYPTO_malloc_init();
      ERR_load_crypto_strings();
      SSL_load_error_strings();
      SSL_library_init();
      OpenSSL_add_all_algorithms();

#if APR_HAS_THREADS
      numlocks = CRYPTO_num_locks();
      apr_pool_create(&ssl_pool, NULL);
      ssl_locks = (apr_thread_mutex_t **)apr_palloc(ssl_pool, sizeof(apr_thread_mutex_t *)*numlocks);
      for (i = 0; i < numlocks; i++) 
      {
         apr_status_t rv;

         rv = apr_thread_mutex_create(&ssl_locks[i], APR_THREAD_MUTEX_DEFAULT, ssl_pool);
         if (rv != APR_SUCCESS)
         {
            errprint("could not create mutex %d of %d", i, numlocks);
            return;
         }
      }
      CRYPTO_set_locking_callback(ssl_lock);
      CRYPTO_set_id_callback(ssl_id);
      CRYPTO_set_dynlock_create_callback(ssl_dyn_create);
      CRYPTO_set_dynlock_lock_callback(ssl_dyn_lock);
      CRYPTO_set_dynlock_destroy_callback(ssl_dyn_destroy);

      apr_pool_cleanup_register(ssl_pool, NULL, cleanup_ssl, cleanup_ssl);
#endif
      have_init_ssl = 1;
   }
}

/** Constructor and initialiser for the SSL libraries. */
SSLInterface::SSLInterface()
{
   init_ssl_libraries();
}

/** Finaliser and destructor for the SSL libraries (this has been mostly
 * replaced with a callback). */
SSLInterface::~SSLInterface()
{
   apr_status_t status;
   status = cleanup_ssl(NULL);
   CheckAPRError(status);

   // This is unsafe - cleaning OpenSSL? - https://github.com/roddi/ValidateStoreReceipt/issues/15
}

/** Get textual error information on this error code.
 * 
 * @param fpretty
 * Pretty printed function name where the error originated from.
 * 
 * @param line
 * Line number where the error originated from. */
void SSLInterface::func_CheckSSLError(const char *fpretty, int line)
{
   // According to the documentation, this is what it wants...
   char static_errbuf[120];
   unsigned long errvalue = ERR_get_error();
   while (errvalue != 0)
   {
      ERR_error_string_n(errvalue, static_errbuf, 120);
      errprintpd("%s", fpretty, line, static_errbuf);
      errvalue = ERR_get_error();
   }
}

/** Destroy the singleton. */
void SSLInterface::destroyInstance()
{
   delete ssl_iface_singleton;
   ssl_iface_singleton = NULL;
}
